﻿#region License
// Copyright © 2004 - 2010 All Right Reserved, Derek Goslin
// 
// 1. Definitions
// The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
// A "contribution" is the original software, or any additions or changes to the software.
// A "contributor" is any person that distributes its contribution under this license.
// "Licensed patents" are a contributor's patent claims that read directly on its contribution.
// 
// 2. Grant of Rights
// (A) Copyright Grant- Subject to the terms of this license, including the license
//     conditions and limitations in section 3, each contributor grants you a non-exclusive,
//     worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative 
//     works of its contribution, and distribute its contribution or any derivative works that you create.
// (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations
//     in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its
//     licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of
//     its contribution in the software or derivative works of the contribution in the software.
// 
// 3. Conditions and Limitations
// (A) Reciprocal Grants- For any file you distribute that contains code from the software (in source code or binary format), 
//     you must provide recipients the source code to that file along with a copy of this license, which license will govern that file. 
//     You may license other files that are entirely your own work and do not contain code from the software under any terms you choose.
// (B) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
// (C) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
//     your patent license from such contributor to the software ends automatically.
// (D) If you distribute any portion of the software, you must retain all copyright, patent, trademark,
//     and attribution notices that are present in the software.
// (E) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy 
//     of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do 
//     so under a license that complies with this license.
// (F) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions.
//    You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your
//    local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
#endregion

using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.Windows.Forms;
using InstrumentationControls.SafeHandles;

namespace InstrumentationControls
{
    public abstract class ScopeBase : Control
    {
        protected delegate void ExceptionHandledDelegate();

        protected object LockObject = new Object();

        #region Constructors

        protected ScopeBase()
        {
            InitaliseControl();
        }

        protected ScopeBase(String text) : base(text)
        {
            InitaliseControl();
        }

        protected ScopeBase(String text, Int32 left, Int32 top, Int32 width, Int32 height)
            : base(text, left, top, width, height)
        {
            InitaliseControl();
        }

        protected ScopeBase(Control parent, String text) : base(parent, text)
        {
            InitaliseControl();
        }

        protected ScopeBase(Control parent, String text, Int32 left, Int32 top, Int32 width, Int32 height)
            : base(parent, text, left, top, width, height)
        {
            InitaliseControl();
        }

        #endregion

        #region Dispose

        private Boolean _disposed;


        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    if (BitmapDisplay != null)
                    {
                        BitmapDisplay.Dispose();
                        BitmapDisplay = null;
                    }

                    if (BitmapGrid != null)
                    {
                        BitmapGrid.Dispose();
                        BitmapGrid = null;
                    }

                    if (BitmapPlot != null)
                    {
                        BitmapPlot.Dispose();
                        BitmapPlot = null;
                    }

                    if (GridPen != null)
                    {
                        GridPen.Dispose();
                        GridPen = null;
                    }

                    if (BackgroundBrush != null)
                    {
                        BackgroundBrush.Dispose();
                        BackgroundBrush = null;
                    }
                }

                _disposed = true;
            }

            base.Dispose(disposing);
        }

        #endregion

        protected internal SolidBrush BackgroundBrush { get; set; }

        protected internal Bitmap BitmapDisplay { get; set; }

        protected internal Bitmap BitmapGrid { get; set; }

        protected internal Bitmap BitmapPlot { get; set; }

        protected internal int DecimalPlaceCount { get; set; }

        protected internal Pen GridPen { get; set; }

        protected internal Rectangle GridRectangle { get; set; }

        protected internal int HalfShiftPixels { get; set; }

        protected internal int PlotShiftPixels { get; set; }

        protected internal int ShiftPixels { get; set; }

        private void InitaliseControl()
        {
            // Set the value of the double-buffering style bits to true.
            SetStyle(ControlStyles.DoubleBuffer | ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);
            UpdateStyles();

            BackgroundBrush = new SolidBrush(base.BackColor);
            GridPen = new Pen(Color.White, 1);
            
            // _nShiftPixels determines how much the plot shifts (in terms of pixels) 
            // with the addition of a new data point
            ShiftPixels = 4;
            HalfShiftPixels = ShiftPixels / 2;
            PlotShiftPixels = ShiftPixels + HalfShiftPixels;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            if ((ClientRectangle.Width != 0) | (ClientRectangle.Height != 0))
            {
                try
                {
                    var paintEventGraphics = e.Graphics;

                    paintEventGraphics.SmoothingMode = SmoothingMode.AntiAlias;

                    using (var hBitMapDest = new GdiObjectSafeHandle(BitmapDisplay.GetHbitmap()))
                    {
                        using (var hdcBitmapDest = new GdiPlusDeviceContextSafeHandle(paintEventGraphics))
                        {
                            using (var hdcMemDest = NativeMethods.CreateCompatibleDC(hdcBitmapDest))
                            {
                                NativeMethods.SelectObject(hdcMemDest, hBitMapDest);

                                using (var gridGraphics = Graphics.FromImage(BitmapGrid))
                                {
                                    using (var hBitMapSrc = new GdiObjectSafeHandle(BitmapGrid.GetHbitmap()))
                                    {
                                        using (var hdcBitmapSrc = new GdiPlusDeviceContextSafeHandle(gridGraphics))
                                        {
                                            using (var hdcMemSrc = NativeMethods.CreateCompatibleDC(hdcBitmapSrc))
                                            {
                                                NativeMethods.SelectObject(hdcMemSrc, hBitMapSrc);

                                                NativeMethods.BitBlt(hdcBitmapDest, 0, 0, ClientSize.Width,
                                                                     ClientSize.Height,
                                                                     hdcMemSrc,
                                                                     0, 0, NativeMethods.RasterOp.Srccopy);
                                            }
                                        }
                                    }

                                    using (var hBitMapSrc = new GdiObjectSafeHandle(BitmapPlot.GetHbitmap()))
                                    {
                                        using (var hdcBitmapSrc = new GdiPlusDeviceContextSafeHandle(gridGraphics))
                                        {
                                            using (var hdcMemSrc = NativeMethods.CreateCompatibleDC(hdcBitmapSrc))
                                            {
                                                NativeMethods.SelectObject(hdcMemSrc, hBitMapSrc);

                                                //NativeMethods.BitBlt(hdcBitmapDest, 0, 0, ClientSize.Width,
                                                //                     ClientSize.Height,
                                                //                     hdcMemSrc,
                                                //                     0, 0, NativeMethods.RasterOp.Srcpaint);
                                                int transparentColor = ColorTranslator.ToWin32(BackColor);

                                                NativeMethods.TransparentBlt(hdcBitmapDest, 0, 0, ClientSize.Width, ClientSize.Height, hdcMemSrc, 0, 0, ClientSize.Width,
                                                                             ClientSize.Height, transparentColor);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    // Display the plot.
                    e.Graphics.DrawImageUnscaled(BitmapDisplay, 0, 0, BitmapGrid.Width, BitmapGrid.Height);
                }
                catch (Exception exp)
                {
                    String className;
                    String methodName;
                    RetrieveClassAndMethodNames(out className, out methodName, 1);

                    Trace.WriteLine(String.Format(CultureInfo.InvariantCulture, "Error: {0}.{1}(): {2}", className, methodName, exp));

                    throw;
                }
            }
        }

        protected static void RetrieveClassAndMethodNames(out String className, out String methodName, int level)
        {
            // leave these assigments as they are because otherwise we need to look for the frame at difffrent levels in debug and release mode
            className = String.Empty;
            methodName = String.Empty;

            var frame = new StackFrame(level);

            className = frame.GetMethod().DeclaringType.Name;
            methodName = frame.GetMethod().Name;
        }

        protected static Bitmap CreateBitmapIfRequired(Bitmap bitmap, Size size, Graphics controlGraphics)
        {
            if (bitmap == null)
            {
                bitmap = new Bitmap(size.Width, size.Height, controlGraphics);
            }
            else if (bitmap.Size != size)
            {
                bitmap = new Bitmap(size.Width, size.Height, controlGraphics);
            }

            return bitmap;
        }

        protected static Int32 DetermineNumberOfCharacters(Double upperLimit, Double lowerLimit, Int32 decimalPlaceCount)
        {
            Int32 nCharacters = 0;

            if (upperLimit != 0)
            {
                nCharacters = Math.Abs((int) Math.Log10(Math.Abs(upperLimit)));
            }

            if (lowerLimit != 0)
            {
                nCharacters = Math.Max(nCharacters, Math.Abs((int) Math.Log10(Math.Abs(lowerLimit))));
            }

            // add the units digit, decimal point and a minus sign, and an extra space
            // as well as the number of decimal places to display
            if (decimalPlaceCount != 0)
            {
                nCharacters++;
            }

            if(lowerLimit < 0)
            {
                nCharacters++;
            }

            nCharacters = nCharacters + 2 + decimalPlaceCount;

            return nCharacters;
        }

        protected void DrawPlotGrid(Graphics gridGraphics)
        {
            Int32 nMidGridPix = (GridRectangle.Top + GridRectangle.Bottom)/2;
            Int32 nTopGridPix = nMidGridPix - GridRectangle.Height/4;
            Int32 nBottomGridPix = nMidGridPix + GridRectangle.Height/4;

            gridGraphics.FillRectangle(BackgroundBrush, 0, 0, BitmapGrid.Width, BitmapGrid.Height);
            gridGraphics.DrawRectangle(GridPen, GridRectangle);

            GridPen.DashStyle = DashStyle.Dash;
            gridGraphics.DrawLine(GridPen, GridRectangle.Left, nMidGridPix, GridRectangle.Right, nMidGridPix);
            gridGraphics.DrawLine(GridPen, GridRectangle.Left, nTopGridPix, GridRectangle.Right, nTopGridPix);
            gridGraphics.DrawLine(GridPen, GridRectangle.Left, nBottomGridPix, GridRectangle.Right, nBottomGridPix);
            GridPen.DashStyle = DashStyle.Solid;
        }

        internal abstract void AppendPoint(Double value, ScopeChannel channel);

        internal abstract void InvalidateControl();

        protected void InvalidateGrid()
        {
            if (ClientSize != new Size(0, 0))
            {
                using (var controlGraphics = CreateGraphics())
                {
                    BitmapGrid = CreateBitmapIfRequired(BitmapGrid, ClientSize, controlGraphics);

                    using (var gridGraphics = Graphics.FromImage(BitmapGrid))
                    {
                        DrawPlotGrid(gridGraphics);

                        DrawPlotLabels(gridGraphics);
                    }
                }
            }
        }

        protected abstract void DrawPlotLabels(Graphics graphics);

        protected static void ExecuteExceptionHandledDelegate(ExceptionHandledDelegate method)
        {   
            try
            {
                method();
            }
            catch (Exception exp)
            {
                String className;
                String methodName;

                RetrieveClassAndMethodNames(out className, out methodName, 1);

                Trace.WriteLine(String.Format(CultureInfo.InvariantCulture, "Error: {0}.{1}(): {2}", className, methodName, exp));



                throw;
            }
        }

        protected void HandleControlCreated()
        {
            base.OnCreateControl();

            InvalidateControl();
        }
    }
}